#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h> // copy to user
#include <asm/io.h>
#include <linux/dma-mapping.h>
#include "git_version.h"


#define DRIVER_NAME "iram"


#define IRAM_ADDR    (phys_addr_t) 0x4000000
#define IRAM_SIZE    (512 * 1024)


static char *version = GIT_VERSION;
module_param(version, charp, S_IRUGO);

void *iram_va = NULL;


static int iram_open(struct inode *inode, struct file *file)
{
    struct device *dev = NULL;
    struct miscdevice *misc_dev = file->private_data;
    char *p;
    dev = misc_dev->this_device;

    pr_info(DRIVER_NAME " open\n");

    iram_va = ioremap(IRAM_ADDR, IRAM_SIZE);
    pr_info("iram[%d] PA=%016llx->VA=%16px.\n", 0, IRAM_ADDR, iram_va);
    p = iram_va;

    memset_io(iram_va, 4, IRAM_SIZE);
    printk("-> %d\n", p[0]);
    printk("-> %d\n", p[1]);
    printk("-> %d\n", p[2]);
    /*
    if (readb(iram_va) != (unsigned char)i) {
        pr_info("failed, stop!\n");
    }
    */

    return 0;
}

static int iram_release(struct inode *inode, struct file *file)
{
    pr_info(DRIVER_NAME " close.\n");
    iounmap(iram_va);
    return 0;
}

static ssize_t iram_read(struct file *file, char __user *buf, size_t len, loff_t *offset)
{
    size_t tmplen = len;
    if (*offset >= IRAM_SIZE) {
        /* read empty */
        pr_info(DRIVER_NAME " read: len=%ld, offset=%lld, EOF.\n", tmplen, *offset);
        return 0;
    }
    if (len > IRAM_SIZE - *offset) {
        len = IRAM_SIZE - *offset;
    }
    if (copy_to_user(buf, iram_va + *offset, len) != 0) {
        /* copy failed */
        pr_info(DRIVER_NAME " read: copy to user failed!\n");
        return -EFAULT;
    }
    *offset += len;
    pr_info(DRIVER_NAME " read: len=%ld, offset=%lld, got=%ld\n", tmplen, *offset, len);
    return len;
}

static ssize_t iram_write(struct file *file, const char __user *buf, size_t len, loff_t *offset)
{
    size_t tmplen = len;
    if (*offset >= IRAM_SIZE) {
        /* write full */
        pr_info(DRIVER_NAME " write: len=%ld, offset=%lld, NOSPACE.\n", len, *offset);
        return -ENOSPC;
    }
    if (len > IRAM_SIZE - *offset) {
        len = IRAM_SIZE - *offset;
    }
    if (copy_from_user(iram_va + *offset, buf, len) != 0) {
        /* copy failed */
        pr_info(DRIVER_NAME " write: copy from user failed!\n");
        return -EFAULT;
    }
    pr_info(DRIVER_NAME " write: len=%ld, offset=%lld, got=%ld.\n", tmplen, *offset, len);
    *offset += len;
    return len;
}

static const struct file_operations iram_fops = {
    .owner = THIS_MODULE,
    .open = iram_open,
    .release = iram_release,
    .read = iram_read,
    .write = iram_write,
};

static struct miscdevice iram_device = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = DRIVER_NAME,
    .fops = &iram_fops,
};

static int __init iram_init(void)
{
    int ret;

    ret = misc_register(&iram_device);
    if (ret < 0) {
        pr_err("Failed to register misc device\n");
        return ret;
    }

    pr_info("MISC device registered\n");

    return 0;
}

static void __exit iram_exit(void)
{
    misc_deregister(&iram_device);
    pr_info("MISC device unregistered\n");
}


module_init(iram_init);
module_exit(iram_exit);


MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple MISC driver example");
